package com.franklin.validation.core.webflux.filter;

import com.franklin.validation.core.cache.RequestMultiValueCache;
import com.franklin.validation.core.constants.Constants;
import com.franklin.validation.core.entity.SimpleEntry;
import com.franklin.validation.core.utils.DataCheckUtil;
import com.franklin.validation.core.utils.IdGenerator;



import com.franklin.validation.core.webflux.override.BodyInserterContext;
import com.franklin.validation.core.webflux.override.CachedBodyOutputMessage;
import com.franklin.validation.core.webflux.override.DefaultServerRequest;
import com.franklin.validation.core.webflux.override.ServerHttpRequestDecorator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpOutputMessage;

import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Description :
 * Create By: Franklin
 * Date : 2020/6/10 15:45
 */
public class ArgumentFilter implements WebFilter {

    @Autowired
    private RequestMultiValueCache<String,String>  cache;

    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
        System.out.println(serverWebExchange.getRequest().getURI().getPath());
        MultiValueMap<String, String> queryParams = serverWebExchange.getRequest().getQueryParams();
        MediaType mediaType = serverWebExchange.getRequest().getHeaders().getContentType();
        DefaultServerRequest serverRequest = new DefaultServerRequest(serverWebExchange);
        String uuid = IdGenerator.get32UUID();
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(serverWebExchange.getRequest().getHeaders());
        headers.add(Constants.REQUEST_ID,uuid);
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        if (DataCheckUtil.isNotEmpty(queryParams)){
            Map<String, List<String>> decodeQueryParams = this.decodeQueryParams(queryParams);
//            ParamsCache.setParams(uuid,decodeQueryParams);
            this.cache.setParams(uuid,decodeQueryParams);
        }
        if (!MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)){
            ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(serverWebExchange.getRequest(),headers,uuid);
            return webFilterChain.filter(serverWebExchange.mutate().request(decorator).build());
        }
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
                .flatMap(body -> {
                    if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) {
                        Map<String, List<String>> params = this.decodeBody(body);
//                        ParamsCache.setParams(uuid, params);
                        this.cache.setParams(uuid,params);
                    }
                    return Mono.just(body);
                });
        BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(serverWebExchange, headers);
        return bodyInserter.insert(outputMessage,  new BodyInserterContext())
                .then(Mono.defer(() -> {
                    ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
                            serverWebExchange.getRequest(),headers,uuid) {
                        @Override
                        public Flux<DataBuffer> getBody() {
                            return outputMessage.getBody();
                        }
                    };
                    return webFilterChain.filter(serverWebExchange.mutate().request(decorator).build());
                })).doFinally(e -> this.cache.release(uuid));
    }

    private Map<String,List<String>> decodeQueryParams(MultiValueMap<String, String> queryParams){
        return queryParams.entrySet()
                .stream()
                .map(entry -> {
                    SimpleEntry<String, List<String>> simpleEntry = new SimpleEntry<>();
                    List<String> value = entry.getValue()
                            .stream()
                            .filter(DataCheckUtil::isNotEmpty)
                            .collect(Collectors.toList());
                    simpleEntry.setKey(entry.getKey());
                    simpleEntry.setValue(value);
                    return simpleEntry;
                })
                .filter(entry -> DataCheckUtil.isNotEmpty(entry.getValue()))
                .collect(Collectors.toMap(SimpleEntry::getKey,SimpleEntry::getValue));
    }

    /**
     * 将body解析为map
     * @param body
     * @return
     */
    private Map<String, List<String>> decodeBody(String body) {
        try {
            body = URLDecoder.decode(body, "utf-8");
        } catch (UnsupportedEncodingException e) {

        }
        return Arrays.stream(body.split("&"))
                .map(kv -> {
                    int firstEQIndex = kv.indexOf("=");
                    SimpleEntry<String, String> entry = new SimpleEntry<>();
                    entry.setKey(kv.substring(0,firstEQIndex));
                    entry.setValue(kv.substring(firstEQIndex + 1));
                    return entry;
                })
                .filter(entry -> DataCheckUtil.isNotEmpty(entry.getValue()))
                .collect(Collectors.groupingBy(SimpleEntry::getKey,
                        Collectors.mapping(SimpleEntry::getValue, Collectors.toList())
                    )
                );
    }

    /**
     * 将map封装成URL参数
     * @param map
     * @return
     */
    private String encodeBody(Map<String, Object> map) {
        return map.entrySet()
                .stream()
                .map(e -> e.getKey() + "=" + e.getValue())
                .collect(Collectors.joining("&"));
    }
}
